`include "common_header.verilog" 
//  *************************************************************************
//  File : p8264_ber_cnt.v
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized reproduction or use is expressly prohibited.
//  Copyright (c) 2010 MorethanIP
//  Muenchner Strasse 199, 85757 Karlsfeld, Germany
//  info@morethanip.com
//  http://www.morethanip.com
//  *************************************************************************
//  Designed by : Thomas Schrobenhauser, Denis Poverennyy
//  info@morethanip.com
//  *************************************************************************
//  Description : BER counter and Hi-BER generation. Configurable Clause49/82
//  Version     : $Id: p8264_ber_cnt.v,v 1.1 2017/12/15 09:59:03 dk Exp $
//  *************************************************************************

module p8264_ber_cnt (
   reset,
   clk,
   const_err_max,
   ber_reset,
   ber_cnt_inc,
   timer_done,
   ber_count_read,
   hi_ber,
   ber_count);
   
input   reset;  //  async active high reset
input   clk;    //  system clock
input   [6:0] const_err_max;    // errors until rising hiber
input   ber_reset;              //  statemachine INIT condition
input   [4:0] ber_cnt_inc;      //  High Bit Error Counter Increment
input   timer_done;             //  Timer expired
input   ber_count_read;         //  BER_count counter has been read 
output   hi_ber;                //  High Bit Error
output   [21:0] ber_count;      //  BER_count counter to be read 

wire    hi_ber; 
reg     [21:0] ber_count; 

// BER measurement
reg     [6:0] bercnt; 
wire    [6:0] bercnt_next; 
wire    ber_cnt_gt_96; 
reg     hi_ber_int; 
wire    [6:0] const_err_max;            // errors until rising hiber 
wire    [1:0] diff_msbs_nc;             // max-current upper bits unused but to have complete subtraction.
wire    [4:0] ber_cnt_diff_max;         // max-current
wire    [4:0] ber_cnt_inc_next;         //  High Bit Error Counter Increment qualified at max increment to saturated value

// error accumulation
reg     ber_count_read_s; 
reg     [21:0] ber_counter; 
wire    [22:0] ber_counter_next; 
reg     [4:0] ber_counter_inc;        //  value to increment the accumulating counter with (valid for 1 cycle, 0 otherwise)


assign bercnt_next      = bercnt + {2'b 00, ber_cnt_inc};

assign ber_cnt_gt_96    = (bercnt_next >= const_err_max) ? 1'b 1 : 1'b 0;

// calculate correct increment when reaching saturation within window count. Must then stop at 97 (i.e. hiber reached) and ignore further errors.
// when timer_done=1 the incoming value is always good, as bercnt is then just now cleared and loaded with that new value

assign {diff_msbs_nc, ber_cnt_diff_max} = const_err_max - bercnt;        // no need for upper bits, but subtraction still must be complete

assign ber_cnt_inc_next = (ber_cnt_gt_96 == 1'b 1 && timer_done == 1'b 0) ? ber_cnt_diff_max[4:0] :       // if done=1 no saturation check need
                          ber_cnt_inc; 


always @(posedge reset or posedge clk)
   begin : process_1
   if (reset == 1'b 1)
      begin
      bercnt <= {7{1'b 0}};	
      hi_ber_int <= 1'b 0;
      end
   else
      begin
      if (ber_reset == 1'b 1)
         begin
         bercnt <= {7{1'b 0}};
         end
      else if (timer_done == 1'b 1)  //  Hi-BER measurement window
         begin
         bercnt <= {2'b 00, ber_cnt_inc};	
         end
      else if (ber_cnt_gt_96 == 1'b 1 )
         begin
         bercnt <= const_err_max;       // saturate                
         end
      else
         begin
         bercnt <= bercnt_next;	
         end

      if (ber_reset == 1'b 1)
         begin
         hi_ber_int <= 1'b 0;	
         end
      else if (ber_cnt_gt_96 == 1'b 1 )
         begin
         hi_ber_int <= 1'b 1;	
         end
      else if (timer_done == 1'b 1 & ber_cnt_gt_96 == 1'b 0 )
         begin
         hi_ber_int <= 1'b 0;	
         end

      end
   end

assign hi_ber = hi_ber_int;

// BER_count counter accumulated until read
// Note that ber_reset is only disabling the measurement (hence suppresses counting) 
// but must not clear the accumulated value.

assign ber_counter_next = {1'b 0, ber_counter} + {18'd 0, ber_counter_inc};

always @(posedge reset or posedge clk)
begin : process_2
        if (reset == 1'b 1)
        begin
                ber_count_read_s <= 1'b 0;	
                ber_counter <= {22{1'b 0}};	
                ber_counter_inc <= 5'd 0;
        end
        else
        begin

                // forward increment to accumulating counter when it is valid.
                
                if( hi_ber_int==1'b 0 && ber_reset==1'b 0 )
                begin
                        ber_counter_inc <= ber_cnt_inc_next;
                end
                else
                begin
                        ber_counter_inc <= 5'd 0;
                end
                
                // read counter -> clear the value - ALL ONE CLOCK CYCLE DELAYED !
                
                ber_count_read_s <= ber_count_read;	
                
                if (ber_count_read_s == 1'b 1)
                begin
                        ber_counter <= {17'd 0, ber_counter_inc};       // do not loose count during read	
                end
                else if ( ber_counter_next[22]==1'b 1 ) // stop counting when saturated.
                begin
                        ber_counter <= {22{1'b 1}};
                end
                else
                begin
                        ber_counter <= ber_counter_next[21:0];
                end

        end
end

// provide a latched output. Value is transferred to output when current value has been read.
// Hence to read the value finally, two consecutive reads are required and both reads to be added.
// This is to allow CDC of new value after read occured, giving time for output to stabilize until next read.
//
always @(posedge reset or posedge clk)
begin : process_3
        if (reset == 1'b 1)
        begin
                ber_count <= {22{1'b 0}};	
        end
        else
        begin
                if (ber_count_read_s == 1'b 1)
                begin
                        ber_count <= ber_counter;
                end
        end
end

endmodule // module p8264_ber_cnt

